﻿using System;
using System.Collections.Generic;
using Needle.Utils.Parser;
using SCM = System.ComponentModel;
using System.Linq.Expressions;
namespace Needle.Utils.Collections
{
    /// <summary></summary>
    public class BindingList<T> : SCM.BindingList<T>, IRichList<T>
    {

        IRichList<T> _items;

        public BindingList()
            : this(new RichList<T>())
        { }

        public BindingList(IRichList<T> list)
            : base(list)
        {
            _items = (IRichList<T>)Items;
        }

        public void AddRange(IEnumerable<T> collection)
        {
            /* Don't use _items, so we direct all Add's through InsertItem(...) */
            foreach (T item in collection)
                Add(item);
        }

        public IRichList<T> AsReadOnly()
        {
            return _items.AsReadOnly();
        }

        public int BinarySearch(T item)
        {
            return _items.BinarySearch(item);
        }

        public int BinarySearch(T item, IComparer<T> comparer)
        {
            return _items.BinarySearch(item, comparer);
        }

        public int BinarySearch(int index, int count, T item, IComparer<T> comparer)
        {
            return _items.BinarySearch(index, count, item, comparer);
        }

        public IRichList<TOutput> ConvertAll<TOutput>(Converter<T, TOutput> converter)
        {
            return _items.ConvertAll(converter);
        }

        public void CopyTo(T[] array)
        {
            _items.CopyTo(array);
        }

        public void CopyTo(int index, T[] array, int arrayIndex, int count)
        {
            _items.CopyTo(index, array, arrayIndex, count);
        }

        public bool Exists(Predicate<T> match)
        {
            return _items.Exists(match);
        }

        public T Find(Predicate<T> match)
        {
            return _items.Find(match);
        }

        public IRichList<T> FindAll(Predicate<T> match)
        {
            return _items.FindAll(match);
        }

        public int FindIndex(Predicate<T> match)
        {
            return _items.FindIndex(match);
        }

        public int FindIndex(int startIndex, Predicate<T> match)
        {
            return _items.FindIndex(startIndex, match);
        }

        public int FindIndex(int startIndex, int count, Predicate<T> match)
        {
            return _items.FindIndex(startIndex, count, match);
        }

        public T FindLast(Predicate<T> match)
        {
            return _items.FindLast(match);
        }

        public int FindLastIndex(Predicate<T> match)
        {
            return _items.FindLastIndex(match);
        }

        public int FindLastIndex(int startIndex, Predicate<T> match)
        {
            return _items.FindLastIndex(startIndex, match);
        }

        public int FindLastIndex(int startIndex, int count, Predicate<T> match)
        {
            return _items.FindLastIndex(startIndex, count, match);
        }

        public void ForEach(Action<T> action)
        {
            _items.ForEach(action);
        }

        public IRichList<T> GetRange(int index, int count)
        {
            return _items.GetRange(index, count);
        }

        public int IndexOf(T item, int index)
        {
            return _items.IndexOf(item, index);
        }

        public int IndexOf(T item, int index, int count)
        {
            return _items.IndexOf(item, index, count);
        }

        public void InsertRange(int index, IEnumerable<T> collection)
        {
            /* Don't use _items, so that all inserts are routed through InsertItem(..) */
            int count = 0;
            foreach (var item in collection)
                Insert(index + count++, item);
        }

        public int LastIndexOf(T item)
        {
            return _items.LastIndexOf(item);
        }

        public int LastIndexOf(T item, int index)
        {
            return _items.LastIndexOf(item, index);
        }

        public int LastIndexOf(T item, int index, int count)
        {
            return _items.LastIndexOf(item, index, count);
        }

        public int RemoveAll(Predicate<T> match)
        {   /* Don't use _items, so that all removes are routed through RemoveItem(..) */
            int removed = 0;
            for (int i = Count - 1; i >= 0; i--)
                if (match(((IList<T>)_items)[i]))
                {
                    RemoveAt(i);
                    removed++;
                }
            return removed;
        }

        public void RemoveRange(int index, int count)
        {
            /* Don't use _items, so that all removes are routed through RemoveItem(..) */
            for (int i = index + count - 1; i >= index; i--)
                RemoveAt(i);
        }

        public void Reverse()
        {
            _items.Reverse();
            if (IsSortedCore)
            {
                _sortDirectionCore =
                    (SortDirectionCore == SCM.ListSortDirection.Ascending)
                    ? SCM.ListSortDirection.Descending
                    : SCM.ListSortDirection.Ascending;
            }
            ResetBindings();
        }

        public void Reverse(int index, int count)
        {
            if (index == 0 && count == this.Count)
            {
                Reverse();
                return;
            }
            RemoveSortCore();
            _items.Reverse(index, count);
            ResetBindings();
        }

        public void Sort()
        {
            RemoveSortCore();
            _items.Sort();
            ResetBindings();
        }

        public void Sort(Comparison<T> comparison)
        {
            RemoveSortCore();
            _items.Sort(comparison);
            ResetBindings();
        }

        public void Sort(IComparer<T> comparer)
        {
            RemoveSortCore();
            _items.Sort(comparer);
            ResetBindings();
        }

        public void Sort(int index, int count, IComparer<T> comparer)
        {
            RemoveSortCore();
            _items.Sort(index, count, comparer);
            ResetBindings();
        }

        public T[] ToArray()
        {
            return _items.ToArray();
        }

        public bool TrueForAll(Predicate<T> match)
        {
            return _items.TrueForAll(match);
        }

        #region Sorting support

        private bool _isSortedCore;
        protected override bool IsSortedCore
        {
            get { return _isSortedCore; }
        }
        private Dictionary<SCM.PropertyDescriptor, IComparer<T>> _sorters = new Dictionary<SCM.PropertyDescriptor, IComparer<T>>();

        protected override void ApplySortCore(SCM.PropertyDescriptor prop, SCM.ListSortDirection direction)
        {
            IComparer<T> sorter = null;

            if (!_sorters.TryGetValue(prop, out sorter))
            {
                Type TSorter = typeof(Sorter<>).MakeGenericType(typeof(T), prop.PropertyType);
                sorter = (IComparer<T>)Activator.CreateInstance(TSorter, prop.Name);
                _sorters.Add(prop, sorter);
            }

            SCM.TypeDescriptor.GetProperties(sorter)["SortDirection"].SetValue(sorter, direction);
            try
            {
                RaiseListChangedEvents = false;
                Sort(sorter);
                _isSortedCore = true;
                _sortPropertyCore = prop;
                _sortDirectionCore = direction;
            }
            finally
            {
                RaiseListChangedEvents = true;
                ResetBindings();
            }
        }

        protected override void RemoveSortCore()
        {
            _isSortedCore = false;
            _sortPropertyCore = null;
            _sortDirectionCore = SCM.ListSortDirection.Ascending;
        }

        private SCM.ListSortDirection _sortDirectionCore;
        protected override SCM.ListSortDirection SortDirectionCore
        {
            get { return _sortDirectionCore; }
        }

        private SCM.PropertyDescriptor _sortPropertyCore;
        protected override SCM.PropertyDescriptor SortPropertyCore
        {
            get { return _sortPropertyCore; }
        }

        protected override bool SupportsSortingCore { get { return true; } }

        private class Sorter<TProperty> : IComparer<T>
        {
            Converter<T, TProperty> _prop;
            Comparer<TProperty> _comparer;
            public SCM.ListSortDirection SortDirection { get; set; }

            public Sorter(string propertyName)
            {
                _comparer = Comparer<TProperty>.Default;
                var pex = Expression.Parameter(typeof(T), "input");
                var mex = Expression.Property(pex, propertyName);
                var converter = Expression.Lambda<Converter<T, TProperty>>(mex, pex);
                _prop = converter.Compile();
            }

            public int Compare(T x, T y)
            {
                int result = _comparer.Compare(_prop(x), _prop(y));
                if (SortDirection == SCM.ListSortDirection.Descending)
                {
                    return -result;
                }
                return result;
            }

        }

        #endregion

        #region Search support

        protected override bool SupportsSearchingCore { get { return true; } }

        private Dictionary<SCM.PropertyDescriptor, object> _finders = new Dictionary<SCM.PropertyDescriptor, object>();

        protected override int FindCore(SCM.PropertyDescriptor prop, object key)
        {
            object finder = null;

            if (!_finders.TryGetValue(prop, out finder))
            {
                Type TFinder = typeof(Finder<>).MakeGenericType(typeof(T), prop.PropertyType);
                finder = (IComparer<T>)Activator.CreateInstance(TFinder, prop);
                _finders.Add(prop, finder);
            }

            SCM.TypeDescriptor.GetProperties(finder)["Key"].SetValue(finder, key);

            if (IsSortedCore && prop.Equals(SortPropertyCore))
            {
                SCM.TypeDescriptor.GetProperties(finder)["SortDirection"].SetValue(finder, SortDirectionCore);
                return BinarySearch(default(T), (IComparer<T>)finder);
            }
            else
            {
                return FindIndex((Predicate<T>)Convert.ChangeType(finder, typeof(Predicate<T>)));
            }
        }

        private class Finder<TProperty> : IComparer<T>, IConvertible
        {
            public TProperty Key { get; set; }
            Converter<T, TProperty> _prop;
            Comparer<TProperty> _comparer;
            public SCM.ListSortDirection SortDirection { get; set; }

            public Finder(SCM.PropertyDescriptor prop)
            {
                _comparer = Comparer<TProperty>.Default;
                var pex = Expression.Parameter(typeof(T), "input");
                var mex = Expression.Property(pex, prop.Name);
                var converter = Expression.Lambda<Converter<T, TProperty>>(mex, pex);
                _prop = converter.Compile();
            }

            public int Compare(T x, T y)
            {
                int result = _comparer.Compare(_prop(x), Key);
                if (SortDirection == SCM.ListSortDirection.Descending)
                {
                    return -result;
                }
                return result;
            }

            public bool Match(T item)
            {
                return _comparer.Compare(_prop(item), Key) == 0;
            }

            public static implicit operator Predicate<T>(Finder<TProperty> cmp)
            {
                return cmp.Match;
            }


            public TypeCode GetTypeCode()
            {
                return TypeCode.Object;
            }

            public bool ToBoolean(IFormatProvider provider)
            {
                throw new NotImplementedException();
            }

            public byte ToByte(IFormatProvider provider)
            {
                throw new NotImplementedException();
            }

            public char ToChar(IFormatProvider provider)
            {
                throw new NotImplementedException();
            }

            public DateTime ToDateTime(IFormatProvider provider)
            {
                throw new NotImplementedException();
            }

            public decimal ToDecimal(IFormatProvider provider)
            {
                throw new NotImplementedException();
            }

            public double ToDouble(IFormatProvider provider)
            {
                throw new NotImplementedException();
            }

            public short ToInt16(IFormatProvider provider)
            {
                throw new NotImplementedException();
            }

            public int ToInt32(IFormatProvider provider)
            {
                throw new NotImplementedException();
            }

            public long ToInt64(IFormatProvider provider)
            {
                throw new NotImplementedException();
            }

            public sbyte ToSByte(IFormatProvider provider)
            {
                throw new NotImplementedException();
            }

            public float ToSingle(IFormatProvider provider)
            {
                throw new NotImplementedException();
            }

            public string ToString(IFormatProvider provider)
            {
                throw new NotImplementedException();
            }

            public object ToType(Type conversionType, IFormatProvider provider)
            {
                if (conversionType == typeof(Predicate<T>))
                    return (Predicate<T>)this;
                throw new ArgumentException();
            }

            public ushort ToUInt16(IFormatProvider provider)
            {
                throw new NotImplementedException();
            }

            public uint ToUInt32(IFormatProvider provider)
            {
                throw new NotImplementedException();
            }

            public ulong ToUInt64(IFormatProvider provider)
            {
                throw new NotImplementedException();
            }
        }

        #endregion

    }

}